
// g++ xlistwin.cc -o xlistwin -lX11 `pkg-config glib-2.0 --cflags --libs`

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <iostream>

#define MAX_PROPERTY_VALUE_LEN 4096

using namespace std;

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo + UBUNTU PATCH for 64bit
static gchar  *get_property(Display *disp, Window win,
                           Atom xa_prop_type, const gchar *prop_name,
                           unsigned long *size)
{
    Atom           xa_prop_name;
    Atom           xa_ret_type;
    int            ret_format;
    unsigned long  ret_nitems;
    unsigned long  ret_bytes_after;
    unsigned long  tmp_size;
    unsigned char *ret_prop;
    gchar         *ret;
    
    xa_prop_name = XInternAtom(disp, prop_name, False);
    
    /* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
     *
     * long_length = Specifies the length in 32-bit multiples of the
     *               data to be retrieved.
     *
     * NOTE:  see 
     * http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
     * In particular:
     *
     * 	When the X window system was ported to 64-bit architectures, a
     * rather peculiar design decision was made. 32-bit quantities such
     * as Window IDs, atoms, etc, were kept as longs in the client side
     * APIs, even when long was changed to 64 bits.
     *
     */
    if(XGetWindowProperty(disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
                          xa_prop_type, &xa_ret_type, &ret_format,     
                          &ret_nitems, &ret_bytes_after, &ret_prop) != Success)
    {
        printf("Cannot get %s property.\n", prop_name);
        return NULL;
    }
    
    if(xa_ret_type != xa_prop_type)
    {
        printf("Invalid type of %s property.\n", prop_name);
        XFree(ret_prop);
        return NULL;
    }
    
    /* null terminate the result to make string handling easier */
    tmp_size = (ret_format / 8) * ret_nitems;
    
    /* UBUNTU PATCH Correct 64 Architecture implementation of 32 bit data */
    if(ret_format==32) tmp_size *= sizeof(long)/4;
    
    ret = (gchar *)g_malloc(tmp_size + 1);
    memcpy(ret, ret_prop, tmp_size);
    ret[tmp_size] = '\0';
    
    if(size)
    {
        *size = tmp_size;
    }
    
    XFree(ret_prop);
    return ret;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static gchar  *get_window_title(Display *disp, Window win)
{
    gchar *title_utf8;
    gchar *wm_name;
    gchar *net_wm_name;
    
    wm_name = get_property(disp, win, XA_STRING, "WM_NAME", NULL);
    net_wm_name = get_property(disp, win, XInternAtom(disp, "UTF8_STRING", False), "_NET_WM_NAME", NULL);
    
    if(net_wm_name)
    {
        title_utf8 = g_strdup(net_wm_name);
    }
    else
    {
        if(wm_name)
        {
            title_utf8 = g_locale_to_utf8(wm_name, -1, NULL, NULL, NULL);
        }
        else
        {
            title_utf8 = NULL;
        }
    }
    
    g_free(wm_name);
    g_free(net_wm_name);
    
    return title_utf8;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static gchar  *get_window_class(Display *disp, Window win)
{
    gchar *class_utf8;
    gchar *wm_class;
    unsigned long size;
    
    wm_class = get_property(disp, win, XA_STRING, "WM_CLASS", &size);
    if(wm_class)
    {
        gchar *p_0 = strchr(wm_class, '\0');
        if(wm_class + size - 1 > p_0)
        {
            *(p_0) = '.';
        }
        class_utf8 = g_locale_to_utf8(wm_class, -1, NULL, NULL, NULL);
    }
    else
    {
        class_utf8 = NULL;
    }
    
    g_free(wm_class);
    
    return class_utf8;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static Window  *get_client_list(Display *disp, unsigned long *size)
{
    Window *client_list = (Window *)get_property(disp, DefaultRootWindow(disp), XA_WINDOW, "_NET_CLIENT_LIST", size);
    if(client_list == NULL)
    {
        client_list = (Window *)get_property(disp, DefaultRootWindow(disp), XA_CARDINAL, "_WIN_CLIENT_LIST", size);
        if(client_list == NULL)
        {
            fputs("Cannot get client list properties. \n"
                  "(_NET_CLIENT_LIST or _WIN_CLIENT_LIST)"
                  "\n", stderr);
            return NULL;
        }
    }
    return client_list;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static int  list_windows(Display *disp, bool no_sticky)
{
    unsigned long  client_list_size;
    Window        *client_list = get_client_list(disp, &client_list_size);
    if(client_list == NULL)
    {
        return EXIT_FAILURE;
    }
    
    /* print the list */
    for(int i = 0; i < client_list_size / sizeof(Window); i++)
    {
        /* desktop ID */
        long *p_desktop = (long *)get_property(disp, client_list[i], XA_CARDINAL, "_NET_WM_DESKTOP", NULL);
        if(p_desktop == NULL)
        {
            p_desktop = (long *)get_property(disp, client_list[i], XA_CARDINAL, "_WIN_WORKSPACE", NULL);
        }
        if(no_sticky && p_desktop && *p_desktop < 0)
        {
            g_free(p_desktop);
            continue;
        }
        
        /* title */
        gchar *title_utf8 = get_window_title(disp, client_list[i]); /* UTF8 */
        
        /* class */
        gchar *class_out = get_window_class(disp, client_list[i]); /* UTF8 */
        
        /* pid */
        unsigned long *p_pid;
        p_pid = (unsigned long *)get_property(disp, client_list[i], XA_CARDINAL, "_NET_WM_PID", NULL);
        
        /* geometry */
        int           x, y, junkx, junky;
        unsigned int  wwidth, wheight, bw, depth;
        Window        junkroot;
        XGetGeometry(disp, client_list[i], &junkroot, &junkx, &junky, &wwidth, &wheight, &bw, &depth);
        XTranslateCoordinates(disp, client_list[i], junkroot, junkx, junky, &x, &y, &junkroot);
        
        cout << "cli=" << client_list[i]
             << ", des=" << (p_desktop ? *p_desktop : 0)
             << ", pid=" << (p_pid ? *p_pid : 0)
             << ", geom=[" << x << ", " << y << ", " << wwidth << ", " << wheight << "]"
             << ",\n\tclass=" << (class_out ? class_out : "N/A")
             << ", title=" << (title_utf8 ? title_utf8 : "N/A") << endl;
        
        g_free(title_utf8);
        g_free(p_desktop);
        g_free(class_out);
        g_free(p_pid);
    }
    g_free(client_list);
    
    return EXIT_SUCCESS;
}

int  main(int argc, char *argv[])
{
    Display *display;
    
    if (! (display = XOpenDisplay(NULL)))
    {
        fputs("Cannot open display.\n", stderr);
        return EXIT_FAILURE;
    }
    
    if(list_windows(display, true/*no_sticky*/) != EXIT_SUCCESS)
    {
        cout << "ERROR: list_windows failed" << endl;
    }
    
    XCloseDisplay(display);
}
